package org.misty.bloomfilter;

import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.JedisNoScriptException;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 只能单线程使用
 */
@Slf4j
public class JedisAdapter implements BloomFilter.IRedisClient<JedisAdapter.Script> {
    private final static ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>();

    private final Jedis client;

    public JedisAdapter(Jedis client) {
        Objects.requireNonNull(client);
        this.client = client;
    }

    @Override
    public Script scriptLoad(String script, Class<?> resultType) {
        var sc = cache.get(script);
        if (!(sc instanceof Script)) {
            while (true) {
                var old = cache.putIfAbsent(script, Boolean.TRUE);
                if (old == Boolean.TRUE) { // 没抢到锁
                    Thread.yield();
                } else if (old instanceof Script) {
                    sc = old;
                    break;
                } else if (old == null) { // 抢到锁
                    var sha = client.scriptLoad(script);
                    var ns = new Script(script, sha);
                    log.info("只有一个线程加载script: {}\n-=-=-\n{}\n-=-=-", ns.sha, script);
                    cache.put(script, sc = ns);
                    break;
                } else {
                    throw new RuntimeException("不兼容的数据: " + old);
                }
            }
        }
        return (Script) sc;
    }


    @Override
    public Object eval(String script, Class<?> resultType, List<String> keys, String... args) {
        Script sc;
        Object ret;
        while (true) {
            sc = scriptLoad(script, resultType);
            try {
                ret = client.evalsha(sc.sha, keys, Arrays.asList(args));
                break;
            } catch (JedisNoScriptException e) {
                cache.remove(script, sc);
            }
        }
        log.info("evalsha <{}>, result: {}", sc.sha, ret);
        if (ret != null) {
            log.info("{}", ret.getClass());
        }
        return ret;
    }

    static class Script {
        String lua;
        String sha;

        Script(String lua, String sha) {
            this.lua = lua;
            this.sha = sha;
        }
    }
}
